/**
 * @file can_loopback_test.c
 * @brief CAN 总线双设备回环测试程序
 *
 * 本程序用于测试两个CAN设备之间的双向通信。
 * 它会创建两个CAN套接字，分别绑定到两个不同的CAN接口（如 can0, can1 或 vcan0, vcan1）。
 * 程序会启动四个线程：
 * - 两个接收线程：每个设备一个，用于接收来自对端的数据并进行校验。
 * - 两个发送线程：每个设备一个，用于向对端发送固定数量（FRAME_COUNT）的数据帧。
 *
 * 程序支持通过命令行参数动态配置CAN接口名称、CAN ID、比特率和采样点。
 * 测试流程会无限循环，每一轮都会重新配置CAN设备、收发数据并校验，最后清理资源。
 *
 * 编译:
 * gcc can_loopback_test.c -o can_loopback_test -lpthread
 *
 * 运行 (需要root权限配置网络接口):
 * sudo ./can_loopback_test <if1> <if2> <id1_tx> <id2_tx> <bitrate> <sample_point>
 *
 * 示例:
 * sudo ./can_loopback_test vcan0 vcan1 123 456 500000 0.875
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <stdbool.h>
#include <pthread.h>
#include <errno.h>

// Linux SocketCAN specific headers
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>

// 定义每次测试循环发送的帧数量
#define FRAME_COUNT 100

/**
 * @brief 线程数据结构
 *
 * 用于向发送和接收线程传递必要的参数和状态。
 */
typedef struct {
    int sock;                   // CAN套接字文件描述符
    const char* ifname;         // CAN接口名称 (e.g., "can0")
    canid_t send_id;            // 本设备发送数据时使用的CAN ID
    canid_t recv_id;            // 本设备期望接收的对端CAN ID
    bool test_success;          // 接收线程的测试结果标志
    int received_count;         // 接收到的有效帧计数
    pthread_t thread_id;        // 存储线程ID
} can_thread_data_t;

/**
 * @brief 打印程序用法说明
 * @param prog_name 程序名称 (argv[0])
 */
void print_usage(const char* prog_name) {
    fprintf(stderr, "CAN 总线双设备回环测试程序\n");
    fprintf(stderr, "用法: sudo %s <if1> <if2> <id1_tx> <id2_tx> <bitrate> <sample_point>\n", prog_name);
    fprintf(stderr, "示例:\n");
    fprintf(stderr, "  sudo %s vcan0 vcan1 123 456 500000 0.875\n", prog_name);
    fprintf(stderr, "参数说明:\n");
    fprintf(stderr, "  <if1>          : 第一个CAN接口名 (例如: can0, vcan0)\n");
    fprintf(stderr, "  <if2>          : 第二个CAN接口名 (例如: can1, vcan1)\n");
    fprintf(stderr, "  <id1_tx>       : 设备1发送时使用的CAN ID (十进制)\n");
    fprintf(stderr, "  <id2_tx>       : 设备2发送时使用的CAN ID (十进制)\n");
    fprintf(stderr, "  <bitrate>      : CAN总线比特率 (例如: 500000 for 500kbit/s)\n");
    fprintf(stderr, "  <sample_point> : 采样点位置 (0到1之间的小数, 例如: 0.875)\n");
}

/**
 * @brief 配置并启动CAN设备
 *
 * 使用iproute2工具集 (ip命令) 来设置CAN接口的比特率、采样点等参数，并启动接口。
 * 这是在Linux上配置CAN接口的标准方法之一。
 *
 * @param ifname 要配置的CAN接口名称
 * @param bitrate 比特率 (单位: bps)
 * @param sample_point 采样点 (0.0 to 1.0)
 * @return 0 表示成功, -1 表示失败
 */
int configure_can_device(const char *ifname, int bitrate, float sample_point) {
    char cmd[256];

    // 1. 关闭CAN接口，以便进行配置
    snprintf(cmd, sizeof(cmd), "ip link set %s down", ifname);
    printf("执行: %s\n", cmd);
    if (system(cmd) != 0) {
        fprintf(stderr, "错误: 执行 '%s' 失败。请检查接口是否存在或权限是否足够。\n", cmd);
        return -1;
    }

    // 2. 设置CAN类型、比特率和采样点
    snprintf(cmd, sizeof(cmd), "ip link set %s type can bitrate %d sample-point %.3f", ifname, bitrate, sample_point);
    printf("执行: %s\n", cmd);
    if (system(cmd) != 0) {
        fprintf(stderr, "错误: 执行 '%s' 失败。可能是参数不支持或权限问题。\n", cmd);
        return -1;
    }

    // 3. 重新启动CAN接口
    snprintf(cmd, sizeof(cmd), "ip link set %s up", ifname);
    printf("执行: %s\n", cmd);
    if (system(cmd) != 0) {
        fprintf(stderr, "错误: 执行 '%s' 失败。\n", cmd);
        return -1;
    }

    return 0;
}

/**
 * @brief 创建并绑定一个CAN套接字
 * @param ifname 要绑定的CAN接口名称
 * @return 成功则返回套接字文件描述符, 失败则返回 -1
 */
int create_can_socket(const char *ifname) {
    int sock;
    struct sockaddr_can addr;
    struct ifreq ifr;

    // 1. 创建 PF_CAN 协议族的 RAW 套接字
    if ((sock = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
        perror("创建Socket失败");
        return -1;
    }

    // 2. 获取CAN接口的索引
    strcpy(ifr.ifr_name, ifname);
    if (ioctl(sock, SIOCGIFINDEX, &ifr) < 0) {
        perror("IOCTL获取接口索引失败");
        close(sock);
        return -1;
    }

    // 3. 绑定套接字到指定的CAN接口
    addr.can_family = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;
    if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("绑定Socket失败");
        close(sock);
        return -1;
    }

    // 4. 设置接收超时，防止线程永久阻塞
    struct timeval tv;
    tv.tv_sec = 2; // 2秒超时
    tv.tv_usec = 0;
    if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv) < 0) {
        perror("设置Socket接收超时失败");
        close(sock);
        return -1;
    }

    return sock;
}

/**
 * @brief CAN数据发送线程函数
 * @param arg 指向 can_thread_data_t 结构的指针
 * @return NULL
 */
void *sender_thread(void *arg) {
    can_thread_data_t *data = (can_thread_data_t *)arg;
    struct can_frame frame;

    // 填充固定的帧信息
    frame.can_id = data->send_id;
    frame.can_dlc = 8; // 数据长度固定为8字节

    printf("发送线程 (%s): 启动，将发送 %d 帧数据, CAN ID: 0x%X\n", data->ifname, FRAME_COUNT, data->send_id);

    for (int i = 0; i < FRAME_COUNT; i++) {
        // 创建可预测的数据负载，用于接收端校验
        // data[0] = 帧序号
        // data[j] = 帧序号 + j
        frame.data[0] = (uint8_t)i;
        for (int j = 1; j < 8; j++) {
            frame.data[j] = (uint8_t)(i + j);
        }

        if (write(data->sock, &frame, sizeof(struct can_frame)) != sizeof(struct can_frame)) {
            perror("发送CAN帧失败");
            // 在实际应用中，这里可能需要更复杂的错误处理
        }
        usleep(1000); // 延时1ms，避免总线过载
    }

    printf("发送线程 (%s): 完成发送 %d 帧。\n", data->ifname, FRAME_COUNT);
    return NULL;
}

/**
 * @brief CAN数据接收与校验线程函数
 * @param arg 指向 can_thread_data_t 结构的指针
 * @return NULL
 */
void *receiver_thread(void *arg) {
    can_thread_data_t *data = (can_thread_data_t *)arg;
    struct can_frame frame;
    data->test_success = false; // 默认测试失败，直到所有帧都成功接收和校验
    data->received_count = 0;

    printf("接收线程 (%s): 启动，等待来自 CAN ID 0x%X 的 %d 帧数据...\n", data->ifname, data->recv_id, FRAME_COUNT);

    while (data->received_count < FRAME_COUNT) {
        ssize_t nbytes = read(data->sock, &frame, sizeof(struct can_frame));

        if (nbytes < 0) {
            fprintf(stderr, "接收线程 (%s): 读取CAN帧失败或超时 (errno: %d, %s)\n", data->ifname, errno, strerror(errno));
            pthread_exit(NULL); // 错误或超时，终止线程
        }

        if (nbytes < sizeof(struct can_frame)) {
            fprintf(stderr, "接收线程 (%s): 收到不完整的CAN帧\n", data->ifname);
            continue;
        }

        // --- 核心校验逻辑 ---
        // 1. 校验CAN ID
        if (frame.can_id != data->recv_id) {
            // 在繁忙的总线上，可能会收到其他ID的报文，这里选择忽略它们
            continue;
        }

        // 2. 校验数据负载
        // 期望的数据负载：data[0] 应该是当前已接收的帧计数
        if (frame.data[0] != (uint8_t)data->received_count) {
            fprintf(stderr, "校验失败 (%s): 帧序号不匹配! 期望: %d, 收到: %d\n",
                    data->ifname, data->received_count, frame.data[0]);
            pthread_exit(NULL); // 数据校验失败，终止线程
        }

        bool data_ok = true;
        for (int j = 1; j < 8; j++) {
            if (frame.data[j] != (uint8_t)(frame.data[0] + j)) {
                fprintf(stderr, "校验失败 (%s): 在第 %d 帧的第 %d 字节数据不匹配!\n",
                        data->ifname, data->received_count, j);
                data_ok = false;
                break;
            }
        }

        if (!data_ok) {
            pthread_exit(NULL); // 数据校验失败，终止线程
        }

        // 如果所有校验都通过，增加计数器
        data->received_count++;
    }

    printf("接收线程 (%s): 成功接收并校验了 %d 帧数据。\n", data->ifname, FRAME_COUNT);
    data->test_success = true; // 所有帧都成功接收并校验
    pthread_exit(NULL);
}


int main(int argc, char *argv[]) {
    // 1. 解析命令行参数
    if (argc != 7) {
        print_usage(argv[0]);
        return 1;
    }

    const char* ifname1 = argv[1];
    const char* ifname2 = argv[2];
    canid_t id1_tx = (canid_t)strtol(argv[3], NULL, 10);
    canid_t id2_tx = (canid_t)strtol(argv[4], NULL, 10);
    int bitrate = atoi(argv[5]);
    float sample_point = atof(argv[6]);

    printf("--- CAN回环测试程序启动 ---\n");
    printf("设备1: %s, 发送ID: 0x%X, 接收ID: 0x%X\n", ifname1, id1_tx, id2_tx);
    printf("设备2: %s, 发送ID: 0x%X, 接收ID: 0x%X\n", ifname2, id2_tx, id1_tx);
    printf("总线参数: 比特率=%d bps, 采样点=%.3f\n\n", bitrate, sample_point);

    int loop_count = 1;
    // 主测试循环
    while (1) {
        printf("\n<<<<<<<<<< 开始第 %d 轮测试 >>>>>>>>>>\n", loop_count++);

        // 2. 配置并启动两个CAN设备
        if (configure_can_device(ifname1, bitrate, sample_point) != 0 ||
            configure_can_device(ifname2, bitrate, sample_point) != 0) {
            fprintf(stderr, "错误: CAN设备配置失败，请检查设置并以root权限运行。\n");
            sleep(5); // 等待一段时间再重试
            continue;
        }
        printf("CAN接口配置成功。\n");

        // 3. 为两个设备创建套接字
        int sock1 = create_can_socket(ifname1);
        int sock2 = create_can_socket(ifname2);
        if (sock1 < 0 || sock2 < 0) {
            fprintf(stderr, "错误: 创建CAN套接字失败。\n");
            if(sock1 >= 0) close(sock1);
            if(sock2 >= 0) close(sock2);
            sleep(5);
            continue;
        }
        printf("CAN套接字创建成功。\n");

        // 4. 准备线程数据
        can_thread_data_t dev1_data = {sock1, ifname1, id1_tx, id2_tx, false, 0, 0};
        can_thread_data_t dev2_data = {sock2, ifname2, id2_tx, id1_tx, false, 0, 0};

        pthread_t rx1_tid, rx2_tid, tx1_tid, tx2_tid;

        // 5. 严格按照“先启动接收，后启动发送”的顺序创建线程
        printf("启动接收线程...\n");
        pthread_create(&rx1_tid, NULL, receiver_thread, &dev1_data);
        pthread_create(&rx2_tid, NULL, receiver_thread, &dev2_data);

        // 短暂等待，确保接收线程已进入监听状态
        usleep(100000); // 100ms

        printf("启动发送线程...\n");
        pthread_create(&tx1_tid, NULL, sender_thread, &dev1_data);
        pthread_create(&tx2_tid, NULL, sender_thread, &dev2_data);

        // 6. 等待所有线程执行完毕
        printf("等待所有线程完成...\n");
        pthread_join(tx1_tid, NULL);
        pthread_join(tx2_tid, NULL);
        pthread_join(rx1_tid, NULL);
        pthread_join(rx2_tid, NULL);
        printf("所有线程已结束。\n");

        // 7. 检查测试结果
        if (dev1_data.test_success && dev2_data.test_success) {
            printf("\n[成功] 第 %d 轮测试通过！两个设备都成功收发并校验了 %d 帧数据。\n", loop_count - 1, FRAME_COUNT);
        } else {
            fprintf(stderr, "\n[失败] 第 %d 轮测试未通过！\n", loop_count - 1);
            if (!dev1_data.test_success) {
                fprintf(stderr, "  -> 设备 %s 在接收 %d 帧后失败或超时。\n", ifname1, dev1_data.received_count);
            }
            if (!dev2_data.test_success) {
                fprintf(stderr, "  -> 设备 %s 在接收 %d 帧后失败或超时。\n", ifname2, dev2_data.received_count);
            }
        }

        // 8. 清理资源：关闭套接字和CAN接口
        close(sock1);
        close(sock2);
        char cmd[128];
        snprintf(cmd, sizeof(cmd), "ip link set %s down", ifname1);
        system(cmd);
        snprintf(cmd, sizeof(cmd), "ip link set %s down", ifname2);
        system(cmd);
        printf("套接字和CAN接口已关闭。\n");

        // 9. 等待一段时间后开始下一轮循环
        printf("<<<<<<<<<< 第 %d 轮测试结束，将在5秒后开始下一轮 >>>>>>>>>>\n", loop_count - 1);
        sleep(5);
    }

    return 0; // 理论上不会执行到这里
}
